/*
 * Copyright (C) 2020 . All rights reserved.
 *
 * File name   : logging.hpp
 * Author      : longbin
 * Created date: 2020-07-06 11:07:56
 * Description : define log api for different platform.
 *
 */

//#pragma once
#ifndef __LOGGING_HPP__
#define __LOGGING_HPP__

#include <iostream>
#include <sstream>
#include <iterator>
#include <string>
#include <vector>
#include <chrono>
#include <ctime>

#ifndef _QNX_SOURCE
#    define _QNX_SOURCE 0
#endif

#ifndef WINDOWS
#    define WINDOWS 0
#endif

#if _QNX_SOURCE // need to define _QNX_SOURCE for qnx
#    include <unistd.h>
#    include <process.h>
#elif WINDOWS // need to define WINDOWS for windows
#    include <process.h>
#    define getpid() _getpid()
#    define gettid() _getpid()
#else // linux
#    include <unistd.h>
#    include <sys/syscall.h>
#    define gettid() syscall(SYS_gettid)
#endif //_QNX_SOURCE

// notice:
//     if the following macros are not defined,
//     the default values will be used.
// LOG_ADD_SYSINFO 0/1
// LOG_ADD_LN      0/1
// _ANDROID_SOURCE     0/1
// LOG_TAG
// LOG_PRINT
// LOG_LEVEL

#ifndef LOG_ADD_SYSINFO // 1-within time-stamp and pid, tid info.
#    define LOG_ADD_SYSINFO 1
#endif // LOG_ADD_SYSINFO

#ifndef LOG_ADD_LN // append log line number of source file
#    define LOG_ADD_LN 1
#endif // LOG_ADD_LN

#ifndef _ANDROID_SOURCE // if true: ALOGI, ALOGD ... will be used instead of LOGI, LOGD ...
#    define _ANDROID_SOURCE 0
#endif

#ifndef LOG_PRINT // should be defined by yourself if needed.
#    define LOG_PRINT std::cout <<
#endif
#ifndef LOG_LEVEL // should be defined by yourself if needed.
#    define LOG_LEVEL logging::LOG_LEVEL_DEBUG
#endif

#ifndef LOG_TAG // should be defined by yourself if needed.
#    define LOG_TAG __FILE__
#endif // LOG_TAG

#if LOG_ADD_LN
#    define LOG_TAG_STR(x) #x
#    define LOG_TAG_STRX(x) LOG_TAG_STR(x)
#    define LOG_LINE_NUM ":" LOG_TAG_STRX(__LINE__)
#else
#    define LOG_LINE_NUM ""
#endif // LOG_ADD_LN

#if _ANDROID_SOURCE

#    define LOGV ALOGV
#    define LOGD ALOGD
#    define LOGI ALOGI
#    define LOGW ALOGW
#    define LOGE ALOGE

#else // _ANDROID_SOURCE

#    if LOG_ADD_SYSINFO
#        define _LOG_PRINT(pri, tag, ln, fmt, ...)                                                 \
            if (pri >= LOG_LEVEL)                                                                  \
            LOG_PRINT(logging::logFormatAll(pri, tag, ln, fmt, ##__VA_ARGS__))
#    else // LOG_ADD_SYSINFO
#        define _LOG_PRINT(pri, tag, ln, fmt, ...)                                                 \
            if (pri >= LOG_LEVEL)                                                                  \
            LOG_PRINT(logging::logFormatContent(pri, tag, ln, fmt, ##__VA_ARGS__))
#    endif // LOG_ADD_SYSINFO

#    define LOGV(fmt, ...)                                                                         \
        _LOG_PRINT(logging::LOG_LEVEL_VERBOSE, LOG_TAG, LOG_LINE_NUM, fmt, ##__VA_ARGS__)
#    define LOGD(fmt, ...)                                                                         \
        _LOG_PRINT(logging::LOG_LEVEL_DEBUG, LOG_TAG, LOG_LINE_NUM, fmt, ##__VA_ARGS__)
#    define LOGI(fmt, ...)                                                                         \
        _LOG_PRINT(logging::LOG_LEVEL_INFO, LOG_TAG, LOG_LINE_NUM, fmt, ##__VA_ARGS__)
#    define LOGW(fmt, ...)                                                                         \
        _LOG_PRINT(logging::LOG_LEVEL_WARN, LOG_TAG, LOG_LINE_NUM, fmt, ##__VA_ARGS__)
#    define LOGE(fmt, ...)                                                                         \
        _LOG_PRINT(logging::LOG_LEVEL_ERROR, LOG_TAG, LOG_LINE_NUM, fmt, ##__VA_ARGS__)
#endif // _ANDROID_SOURCE

namespace logging
{
enum {
    LOG_LEVEL_DEFAULT = 0,
    LOG_LEVEL_VERBOSE,
    LOG_LEVEL_DEBUG,
    LOG_LEVEL_INFO,
    LOG_LEVEL_WARN,
    LOG_LEVEL_ERROR,
    LOG_LEVEL_FATAL,
};

template <class S>
std::string join(const S &sequence, const std::string &delim = "")
{
    if (0 == sequence.size()) {
        return {};
    }
    using U = typename std::decay<typename S::value_type>::type;
    std::ostringstream oss{};
    std::copy(sequence.begin(), sequence.end(), std::ostream_iterator<U>(oss, delim.c_str()));
    std::string res(oss.str());
    return {res.c_str(), res.length() - delim.length()};
}

template <typename... Args>
inline std::string logFormatString(const char *fmt)
{
    return (fmt) ? fmt : "";
}

template <typename... Args>
inline std::string logFormatString(const char *fmt, Args &&... args)
{
    std::vector<char> output(160);
    int newlen = snprintf(output.data(), output.size(), fmt, args...);

    if ((size_t)(newlen + 1) > output.size()) {
        // string was truncated.
        output.resize((size_t)(newlen + 1)); // add '\0'.
        newlen = snprintf(output.data(), output.size(), fmt, args...);
    }
    // (newlen < 0): just an error.
    return (newlen > 0) ? output.data() : "";
}

template <typename... Args>
inline std::string logFormatContent(int prio, const char *tag, const char *ln, const char *fmt,
                                    Args &&... args)
{
    // the first character of each string should be unique.
    static const char *priorities[] = {"Verbose", "Debug", "Info", "Warn", "Error", "Fatal"};
    static const size_t priorityCount = (sizeof(priorities) / sizeof(priorities[0]));

    char label = '?';
    size_t level = (size_t)(prio - (int)LOG_LEVEL_VERBOSE);
    if (level < priorityCount) {
        if (nullptr != priorities[level]) {
            label = priorities[level][0];
        }
    }

    std::string output = logFormatString("%c %s%s ", label, tag, ln); // pri, tag, ln
    output += logFormatString(fmt, std::forward<Args>(args)...);      // string
    if (output.back() != '\n') {
        output.append("\n");
    }

    return output;
}

template <typename... Args>
inline std::string logFormatAll(int prio, const char *tag, const char *ln, const char *fmt,
                                Args &&... args)
{
    static int pid = (int)getpid();
    int tid = (int)gettid();

    std::chrono::time_point<std::chrono::system_clock, std::chrono::microseconds> now =
        std::chrono::time_point_cast<std::chrono::microseconds>(std::chrono::system_clock::now());
    std::chrono::microseconds drtn =
        std::chrono::duration_cast<std::chrono::microseconds>(now.time_since_epoch());

    long int microsLeft = std::chrono::duration_cast<std::chrono::microseconds>(
                              drtn - std::chrono::duration_cast<std::chrono::seconds>(drtn))
                              .count();
    std::time_t when = std::chrono::system_clock::to_time_t(now);

#if WINDOWS
    std::tm tmo{};
    std::tm *ptm = &tmo;
    (void)localtime_s(ptm, &when);
#else
    std::tm *ptm = std::localtime(&when);
#endif

    // the first character of each string should be unique.
    static const char *priorities[] = {"Verbose", "Debug", "Info", "Warn", "Error", "Fatal"};
    static const size_t priorityCount = (sizeof(priorities) / sizeof(priorities[0]));

    char label = '?';
    size_t level = (size_t)(prio - (int)LOG_LEVEL_VERBOSE);
    if (level < priorityCount) {
        if (nullptr != priorities[level]) {
            label = priorities[level][0];
        }
    }

    std::string output = logFormatString(
        "%04u-%02u-%02u %02u:%02u:%02u.%06ld %d %d %c %s%s ", ptm->tm_year + 1900, ptm->tm_mon + 1,
        ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec, microsLeft, (int)pid, (int)tid, label,
        tag, ln);                                                // timestamp pid tid prio tag ln
    output += logFormatString(fmt, std::forward<Args>(args)...); // string
    if (output.back() != '\n') {
        output.append("\n");
    }

    return output;
}

}; // namespace logging

#endif // __LOGGING_HPP__
